#!/usr/bin/python
#
# @file netopeer-manager
# @author David Kupka <dkupka@cesnet.cz>
# @author Radek Krejci <rkrejci@cesnet.cz>
# @brief Netopeer modules manager
#
# Copyright (c) 2014 CESNET, z.s.p.o.
# All rights reserved.
#
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 3. Neither the name of the CESNET, z.s.p.o. nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

try:
	import argparse
	import os
	import string
	import xml
	from xml.dom import minidom
	from xml.dom.minidom import getDOMImplementation
except ImportError as e:
	print(e.message+'. Please install missing package.')
	exit(1)


def getText(nodelist):
    rc = []
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE:
            rc.append(node.data)
    return ''.join(rc)

def getNetopeerDatastore(config):
	repo = config.getElementsByTagName('repo')[0]
	return getText(repo.getElementsByTagName('path')[0].childNodes)

def listModules(config):
	list = []
	startup = config.getElementsByTagName('startup')[0]
	modules = startup.getElementsByTagName('module')
	for module in modules:
		item = []
		item.append(getText(module.getElementsByTagName('name')[0].childNodes))
		if string.lower(getText(module.getElementsByTagName('enabled')[0].childNodes)) == 'true':
			item.append('enabled')
		else:
			item.append('disabled')
		list.append(item)
	return list

def getModule(config, name):
	startup = config.getElementsByTagName('startup')[0]
	modules = startup.getElementsByTagName('module')
	for module in modules:
		if getText(module.getElementsByTagName('name')[0].childNodes) == name:
			return module
	return None

def getModel(config, path):
	models = config.getElementsByTagName('model')
	for model in models:
		node = model.getElementsByTagName('path')[0]
		if getText(node.childNodes) == path:
			return model
	return None

def listAugments(config):
	list = []
	augments = config.getElementsByTagName('model')
	for model in augments:
		item = []
		item.append(getText(model.getElementsByTagName('path')[0].childNodes))
		tapi = getText(model.getElementsByTagName('transapi')[0].childNodes)
		if tapi == '':
			item.append('no transAPI module')
		else:
			item.append(tapi)
		list.append(item)
	return list

# "main" starts here
# create argument parser
parser = argparse.ArgumentParser(description="Manage Netopeer's modules", formatter_class=argparse.RawTextHelpFormatter)
subparsers = parser.add_subparsers(dest='subcommand')
parser_add = subparsers.add_parser('add', help='Add a new module.')
parser_list = subparsers.add_parser('list', help='List modules.')
parser_rm = subparsers.add_parser('rm', help='Remove a module.')

parser_add.add_argument('--name', required=True, help='Name (ID) of the module.')
model_group = parser_add.add_mutually_exclusive_group(required=True)
model_group.add_argument('--model', type=argparse.FileType('r'), help='File holding the main data model (YIN format).')
model_group.add_argument('--augment', type=argparse.FileType('r'), help='File holding an augment model (YIN format).')
model_group.add_argument('--import', type=argparse.FileType('r'), help='File holding an imported model (YIN format).')
parser_add.add_argument('--transapi', type=argparse.FileType('r'), help='File holding the transAPI module (.so) for the main data model.')
parser_add.add_argument('--features', nargs='+', action='append', help='List of enabled features. By default, all features are disabled. To enable all features, use \'*\' character.')
parser_add.add_argument('--datastore', help='File path to the datastore location. If not set, datastore will not be able to store configuration data')

parser_list.add_argument('--name', help='If listing augment modules, the name of the main module.')

parser_rm.add_argument('--name', required=True, help='Name of the main module to remove.')
parser_rm.add_argument('--model', type=argparse.FileType('r'), help='Specify augment or import model file of the main module to be removed instead.')

modules_path = '@MODULESDIR@'
netopeer_config = '/etc/netopeer/cfgnetopeer/datastore.xml'

try:
	args = parser.parse_args()

	# get Netopeer configuration datastore
	if not os.path.exists(modules_path+'/Netopeer.xml'):
		print('Missing Netopeer module description ('+modules_path+'/Netopeer.xml)')
		exit(1)

	netopeer_config = getNetopeerDatastore(minidom.parse(modules_path+'/Netopeer.xml'))
	if not os.path.exists(netopeer_config):
		print('Missing Netopeer configuration datastore ('+netopeer_config+')')
		exit(1)

	if args.subcommand == 'list':
		print('Reading the configuration from '+modules_path)
		if args.name:
			module_config = modules_path+'/'+args.name+'.xml'
			if not os.path.exists(module_config):
				print('Missing module configuration file ('+module_config+')')
			list = listAugments(minidom.parse(module_config))

			if list == []:
				print('No augment models for '+args.name+' model')
			else:
				print('List of '+args.name+'\'s augment models:')
				for item in list:
					print(item[0]+' ('+item[1]+')')
		else:
			try:
				config = minidom.parse(netopeer_config)
				list = listModules(minidom.parse(netopeer_config))
			except xml.parsers.expat.ExpatError:
				list = []

			if list == []:
				print('No module installed.')
			else:
				print('List of startup Netopeer modules:')
				for item in list:
					print(item[0]+' ('+item[1]+')')

	elif args.subcommand == 'add':
		if args.model:
			try:
				config = minidom.parse(netopeer_config)
			except xml.parsers.expat.ExpatError:
				impl = getDOMImplementation()
				config = impl.createDocument(None, None, None)
				datastores = config.appendChild(config.createElementNS('urn:cesnet:tmc:datastores:file', 'datastores'))
				datastores.setAttribute('xmlns', 'urn:cesnet:tmc:datastores:file')
				candidate = datastores.appendChild(config.createElement('candidate'))
				candidate.setAttribute('lock', '')
				candidate.setAttribute('modified', 'false')
				running = datastores.appendChild(config.createElement('running'))
				running.setAttribute('lock', '')
				startup = datastores.appendChild(config.createElement('startup'))
				startup.setAttribute('lock', '')

			list = listModules(config)
			for i in list:
				if i[0] == args.name:
					print('The module you are trying to add already exists.')
					exit(1)

			startup = config.getElementsByTagName('startup')[0]
			try:
				modules = startup.getElementsByTagName('modules')[0]
			except IndexError:
				try:
					netopeer = startup.getElementsByTagName('netopeer')[0]
				except IndexError:
					netopeer = startup.appendChild(config.createElementNS('urn:cesnet:tmc:netopeer:1.0','netopeer'))
					netopeer.setAttribute('xmlns', 'urn:cesnet:tmc:netopeer:1.0')
				modules = netopeer.appendChild(config.createElement('modules'))

			module = modules.appendChild(config.createElement('module'))

			node = module.appendChild(config.createElement('name'))
			node.appendChild(config.createTextNode(args.name))
			node = module.appendChild(config.createElement('enabled'))
			node.appendChild(config.createTextNode('true'))
			config.writexml(open(netopeer_config, 'w'))

			impl = getDOMImplementation()
			config = impl.createDocument(None, 'device', None)
			root = config.firstChild

			node = root.appendChild(config.createElement('name'))
			node.appendChild(config.createTextNode(args.name))
			models = root.appendChild(config.createElement('data-models'))
			model = models.appendChild(config.createElement('model-main'))

		else:
			import_arg = getattr(args, 'import')
			try:
				config = minidom.parse(netopeer_config)
			except xml.parsers.expat.ExpatError:
				print('Netopeer module configuration could not be parsed.')
				exit(1)

			list = listModules(config)
			exists = False
			for i in list:
				if i[0] == args.name:
					exists = True

			if not exists:
				print('The main module you are trying to add models to does not exist.')
				exit(1)

			config = minidom.parse(modules_path+'/'+args.name+'.xml')
			model = getModel(config, os.path.abspath(args.augment.name if args.augment else import_arg.name))
			if not model is None:
				print('Such model already exists in \''+args.name+'\' module.')
				exit(1)

			models = config.getElementsByTagName('data-models')[0]
			if args.augment:
				model = models.appendChild(config.createElement('model'))
			else:
				model = models.insertBefore(config.createElement('model'), models.firstChild)

		node = model.appendChild(config.createElement('path'))
		node.appendChild(config.createTextNode(os.path.abspath(\
			args.model.name if args.model else args.augment.name if args.augment else import_arg.name)))
		if args.transapi:
			node = model.appendChild(config.createElement('transapi'))
			node.appendChild(config.createTextNode(os.path.abspath(args.transapi.name)))

		if args.features:
			for l in args.features:
				for f in l:
					node = model.appendChild(config.createElement('feature'))
					node.appendChild(config.createTextNode(f))

		if args.model:
			repo = root.appendChild(config.createElement('repo'))
			node = repo.appendChild(config.createElement('type'))
			if args.datastore:
				node.appendChild(config.createTextNode('file'))
				node = repo.appendChild(config.createElement('path'))
				node.appendChild(config.createTextNode(os.path.abspath(args.datastore)))
			else:
				node.appendChild(config.createTextNode('empty'))

		config.writexml(open(modules_path+'/'+args.name+'.xml', 'w'))

	elif args.subcommand == 'rm':
		if args.model:
			try:
				config = minidom.parse(modules_path+'/'+args.name+'.xml')
				model = getModel(config, os.path.abspath(args.model.name))
			except xml.parsers.expat.ExpatError:
				model = None

			if model is None:
				print('No such model in \''+args.name+'\' module found.')
				exit(1)

			model.parentNode.removeChild(model)
			model.unlink
			config.writexml(open(modules_path+'/'+args.name+'.xml', 'w'))

		else:
			try:
				config = minidom.parse(netopeer_config)
				module = getModule(config, args.name)
			except xml.parsers.expat.ExpatError:
				module = None

			if module is None:
				print('No \''+args.name+'\' module found.')
				exit(1)

			module.parentNode.removeChild(module)
			module.unlink
			config.writexml(open(netopeer_config, 'w'))
			try:
				os.remove(modules_path+'/'+args.name+'.xml')
			except OSError:
				pass

except ValueError as e:
	print (e)
except IOError as e:
	print(e[1]+'('+str(e[0])+'): '+e.filename)

os.sys.exit(0)

